/*
 * 该文件是为了IOS使用硬编解码来实现H264
 */

#include <pjmedia-codec/videotoolbox.h>
#include <pjmedia-codec/h264_packetizer.h>
#include <pjmedia/errno.h>
#include <pjmedia/vid_codec_util.h>
#include <pj/assert.h>
#include <pj/list.h>
#include <pj/log.h>
#include <pj/math.h>
#include <pj/pool.h>
#include <pj/string.h>
#include <pj/os.h>


/*
 * Only build this file if PJMEDIA_HAS_VIDEOTOOLBOX_VID_CODEC != 0 and
 * PJMEDIA_HAS_VIDEO != 0
 */
#if defined(PJMEDIA_HAS_VIDEOTOOLBOX_VID_CODEC) && \
    PJMEDIA_HAS_VIDEOTOOLBOX_VID_CODEC != 0 && \
defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)

#define THIS_FILE   "videotoolbox.m"

#  define DEFAULT_WIDTH		352
#  define DEFAULT_HEIGHT	288

#define DEFAULT_FPS		15
#define DEFAULT_AVG_BITRATE	256000
#define DEFAULT_MAX_BITRATE	256000

#define MAX_RX_WIDTH		1200
#define MAX_RX_HEIGHT		800

/* Prototypes for VIDEOTOOLBOX codecs factory */
static pj_status_t videotoolbox_test_alloc( pjmedia_vid_codec_factory *factory,
    const pjmedia_vid_codec_info *id );
static pj_status_t videotoolbox_default_attr( pjmedia_vid_codec_factory *factory,
    const pjmedia_vid_codec_info *info,
    pjmedia_vid_codec_param *attr );
static pj_status_t videotoolbox_enum_info( pjmedia_vid_codec_factory *factory,
    unsigned *count,
    pjmedia_vid_codec_info codecs[]);
static pj_status_t videotoolbox_alloc_codec( pjmedia_vid_codec_factory *factory,
    const pjmedia_vid_codec_info *info,
    pjmedia_vid_codec **p_codec);
static pj_status_t videotoolbox_dealloc_codec( pjmedia_vid_codec_factory *factory,
    pjmedia_vid_codec *codec );

/* Prototypes for VIDEOTOOLBOX codecs implementation. */
static pj_status_t  videotoolbox_codec_init( pjmedia_vid_codec *codec,
    pj_pool_t *pool );
static pj_status_t  videotoolbox_codec_open( pjmedia_vid_codec *codec,
    pjmedia_vid_codec_param *attr );
static pj_status_t  videotoolbox_codec_close( pjmedia_vid_codec *codec );
static pj_status_t  videotoolbox_codec_modify(pjmedia_vid_codec *codec,
    const pjmedia_vid_codec_param *attr );
static pj_status_t  videotoolbox_codec_get_param(pjmedia_vid_codec *codec,
    pjmedia_vid_codec_param *param);
static pj_status_t videotoolbox_codec_encode_begin(pjmedia_vid_codec *codec,
    const pjmedia_vid_encode_opt *opt,
    const pjmedia_frame *input,
    unsigned out_size,
    pjmedia_frame *output,
    pj_bool_t *has_more);
static pj_status_t videotoolbox_codec_encode_more(pjmedia_vid_codec *codec,
    unsigned out_size,
    pjmedia_frame *output,
    pj_bool_t *has_more);
static pj_status_t videotoolbox_codec_decode( pjmedia_vid_codec *codec,
    pj_size_t pkt_count,
    pjmedia_frame packets[],
    unsigned out_size,
    pjmedia_frame *output);

/* Definition for VIDEOTOOLBOX codecs operations. */
static pjmedia_vid_codec_op videotoolbox_op =
{
    &videotoolbox_codec_init,
    &videotoolbox_codec_open,
    &videotoolbox_codec_close,
    &videotoolbox_codec_modify,
    &videotoolbox_codec_get_param,
    &videotoolbox_codec_encode_begin,
    &videotoolbox_codec_encode_more,
    &videotoolbox_codec_decode,
    NULL
};

/* Definition for VIDEOTOOLBOX codecs factory operations. */
static pjmedia_vid_codec_factory_op videotoolbox_factory_op =
{
    &videotoolbox_test_alloc,
    &videotoolbox_default_attr,
    &videotoolbox_enum_info,
    &videotoolbox_alloc_codec,
    &videotoolbox_dealloc_codec
};

static struct videotoolbox_factory
{
    pjmedia_vid_codec_factory    base;
    pjmedia_vid_codec_mgr	     *mgr;
    pj_pool_factory              *pf;
    pj_pool_t		             *pool;
}videotoolbox_factory;

typedef struct videotoolbox_private
{
    pj_pool_t *pool;
    pjmedia_vid_codec_param	*prm;
    pj_bool_t			 whole;
    pjmedia_h264_packetizer	*pktz;

    /* Encoder state */
    unsigned		 	 enc_input_size;
    pj_uint8_t			*enc_frame_whole;
    unsigned			 enc_frame_size;
    unsigned			 enc_processed;

    /* Decoder state */
    unsigned			 dec_buf_size;
    pj_uint8_t			*dec_buf;
}videotoolbox_private;

/*
 * Initialize and register VIDEOTOOLBOX codec factory to pjmedia endpoint.
 */
PJ_DEF(pj_status_t) pjmedia_codec_videotoolbox_vid_init(pjmedia_vid_codec_mgr *mgr,
    pj_pool_factory *pf)
{
    const pj_str_t h264_name = { (char*)"H264", 4};
    pj_status_t status;

    if (videotoolbox_factory.pool != NULL)
    {
        /* Already initialized. */
        return PJ_SUCCESS;
    }

    if (!mgr) mgr = pjmedia_vid_codec_mgr_instance();
    PJ_ASSERT_RETURN(mgr, PJ_EINVAL);

    /* Create VIDEOTOOLBOX codec factory. */
    videotoolbox_factory.base.op = &videotoolbox_factory_op;
    videotoolbox_factory.base.factory_data = NULL;
    videotoolbox_factory.mgr = mgr;
    videotoolbox_factory.pf  = pf;

    videotoolbox_factory.pool = pj_pool_create(pf, "videotoolbox factory", 256, 256, NULL);
    if (!videotoolbox_factory.pool)
        return PJ_ENOMEM;

    /* Registering format match for SDP negotiation */
    status = pjmedia_sdp_neg_register_fmt_match_cb(
        &h264_name,
        &pjmedia_vid_codec_h264_match_sdp);
    if (status != PJ_SUCCESS)
        goto on_error;

    /* Register codec factory to codec manager. */
    status = pjmedia_vid_codec_mgr_register_factory(mgr,
        &videotoolbox_factory.base);
    if (status != PJ_SUCCESS)
    {
      goto on_error;
    }

    PJ_LOG(4,(THIS_FILE, "VIDEOTOOLBOX codec initialized"));

    /* Done. */
    return PJ_SUCCESS;

on_error:
    pj_pool_release(videotoolbox_factory.pool);
    videotoolbox_factory.pool = NULL;
    return status;
}

/*
 * Unregister VIDEOTOOLBOX codecs factory from pjmedia endpoint.
 */
PJ_DEF(pj_status_t) pjmedia_codec_videotoolbox_vid_deinit(void)
{
    pj_status_t status = PJ_SUCCESS;

    if (videotoolbox_factory.pool == NULL) {
        /* Already deinitialized */
        return PJ_SUCCESS;
    }

    /* Unregister VideoToolBox codecs factory. */
    status = pjmedia_vid_codec_mgr_unregister_factory(videotoolbox_factory.mgr,
        &videotoolbox_factory.base);

    /* Destroy pool. */
    pj_pool_release(videotoolbox_factory.pool);
    videotoolbox_factory.pool = NULL;

    return status;
}

/*
 * Check if factory can allocate the specified codec.
 */
static pj_status_t videotoolbox_test_alloc(pjmedia_vid_codec_factory *factory,
    const pjmedia_vid_codec_info *info )
{
    PJ_ASSERT_RETURN(factory == &videotoolbox_factory.base, PJ_EINVAL);

    if (info->fmt_id == PJMEDIA_FORMAT_H264 &&
        info->pt != 0)
    {
        return PJ_SUCCESS;
    }

    return PJMEDIA_CODEC_EUNSUP;
}

/*
 * Generate default attribute.
 */
static pj_status_t videotoolbox_default_attr(pjmedia_vid_codec_factory *factory,
    const pjmedia_vid_codec_info *info,
    pjmedia_vid_codec_param *attr )
{
    PJ_ASSERT_RETURN(factory == &videotoolbox_factory.base, PJ_EINVAL);
    PJ_ASSERT_RETURN(info && attr, PJ_EINVAL);

    pj_bzero(attr, sizeof(pjmedia_vid_codec_param));

    attr->dir = PJMEDIA_DIR_ENCODING_DECODING;
    attr->packing = PJMEDIA_VID_PACKING_PACKETS;

    /* Encoded format */
    pjmedia_format_init_video(&attr->enc_fmt, PJMEDIA_FORMAT_H264,
        DEFAULT_WIDTH, DEFAULT_HEIGHT,
        DEFAULT_FPS, 1);

    /* Decoded format */
    pjmedia_format_init_video(&attr->dec_fmt, PJMEDIA_FORMAT_I420,
        DEFAULT_WIDTH, DEFAULT_HEIGHT,
        DEFAULT_FPS, 1);

    /* Decoding fmtp */
    attr->dec_fmtp.cnt = 2;
    attr->dec_fmtp.param[0].name = pj_str((char*)"profile-level-id");
    attr->dec_fmtp.param[0].val = pj_str((char*)"42e01e");
    attr->dec_fmtp.param[1].name = pj_str((char*)" packetization-mode");
    attr->dec_fmtp.param[1].val = pj_str((char*)"1");

    /* Bitrate */
    attr->enc_fmt.det.vid.avg_bps = DEFAULT_AVG_BITRATE;
    attr->enc_fmt.det.vid.max_bps = DEFAULT_MAX_BITRATE;

    /* Encoding MTU */
    attr->enc_mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;

    return PJ_SUCCESS;
}

/*
 * Enum codecs supported by this factory.
 */
static pj_status_t videotoolbox_enum_info(pjmedia_vid_codec_factory *factory,
    unsigned *count,
    pjmedia_vid_codec_info info[])
{
    PJ_ASSERT_RETURN(info && *count > 0, PJ_EINVAL);
    PJ_ASSERT_RETURN(factory == &videotoolbox_factory.base, PJ_EINVAL);

    *count = 1;
    info->fmt_id = PJMEDIA_FORMAT_H264;
//    info->pt = PJMEDIA_RTP_PT_H264;
    info->encoding_name = pj_str((char*)"H264");
    info->encoding_desc = pj_str((char*)"VIDEOTOOLBOX codec");
    info->clock_rate = 90000;
    info->dir = PJMEDIA_DIR_ENCODING_DECODING;
    info->dec_fmt_id_cnt = 1;
    info->dec_fmt_id[0] = PJMEDIA_FORMAT_I420;
    info->packings = PJMEDIA_VID_PACKING_PACKETS | PJMEDIA_VID_PACKING_WHOLE;
    info->fps_cnt = 3;
    info->fps[0].num = 15;
    info->fps[0].denum = 1;
    info->fps[1].num = 25;
    info->fps[1].denum = 1;
    info->fps[2].num = 30;
    info->fps[2].denum = 1;

    return PJ_SUCCESS;

}

/*
 * Allocate a new codec instance.
 */
static pj_status_t videotoolbox_alloc_codec(pjmedia_vid_codec_factory *factory,
    const pjmedia_vid_codec_info *info,
    pjmedia_vid_codec **p_codec)
{
    pj_pool_t *pool;
    pj_status_t status = PJ_SUCCESS;
    pjmedia_vid_codec *codec;
    videotoolbox_private *videotoolbox_data;

    PJ_ASSERT_RETURN(factory == &videotoolbox_factory.base && info && p_codec,
        PJ_EINVAL);

    *p_codec = NULL;

    pool = pj_pool_create(videotoolbox_factory.pf, "videotoolbox%p", 512, 512, NULL);
    if (!pool)
        return PJ_ENOMEM;

    /* codec instance */
    codec = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec);
    if (!codec) {
        status = PJ_ENOMEM;
        goto on_error;
    }
    codec->factory = factory;
    codec->op = &videotoolbox_op;

    /* codec data */
    videotoolbox_data = PJ_POOL_ZALLOC_T(pool, videotoolbox_private);
    if (!videotoolbox_data) {
        status = PJ_ENOMEM;
        goto on_error;
    }
    videotoolbox_data->pool = pool;
    codec->codec_data = videotoolbox_data;

    /* encoder allocation */
    //   rc = WelsCreateSVCEncoder(&oh264_data->enc);
    //   if (rc != 0)
    // goto on_error;
    //
    //   oh264_data->esrc_pic = PJ_POOL_ZALLOC_T(pool, SSourcePicture);
    //
    //   /* decoder allocation */
    //   rc = WelsCreateDecoder(&oh264_data->dec);
    //   if (rc != 0)
    // goto on_error;

    *p_codec = codec;
    return PJ_SUCCESS;

on_error:
    videotoolbox_dealloc_codec(factory, codec);
    return PJMEDIA_CODEC_EFAILED;
}

/*
 * Free codec.
 */
static pj_status_t videotoolbox_dealloc_codec(pjmedia_vid_codec_factory *factory,
    pjmedia_vid_codec *codec )
{
    videotoolbox_private *videotoolbox_data;

    PJ_ASSERT_RETURN(codec, PJ_EINVAL);

    PJ_UNUSED_ARG(factory);

    videotoolbox_data = (videotoolbox_private*) codec->codec_data;
    //    if (videotoolbox_data->enc) {
    // 	WelsDestroySVCEncoder(videotoolbox_data->enc);
    // 	videotoolbox_data->enc = NULL;
    //    }
    //    if (videotoolbox_data->dec) {
    // 	videotoolbox_data->dec->Uninitialize();
    //    	WelsDestroyDecoder(videotoolbox_data->dec);
    //    	videotoolbox_data->dec = NULL;
    //    }
    pj_pool_release(videotoolbox_data->pool);
    return PJ_SUCCESS;
}

/*
 * Init codec.
 */
static pj_status_t videotoolbox_codec_init( pjmedia_vid_codec *codec,
    pj_pool_t *pool )
{
    PJ_ASSERT_RETURN(codec && pool, PJ_EINVAL);
    PJ_UNUSED_ARG(codec);
    PJ_UNUSED_ARG(pool);
    return PJ_SUCCESS;
}

/*
 * Open codec.
 */
static pj_status_t videotoolbox_codec_open(pjmedia_vid_codec *codec,
    pjmedia_vid_codec_param *codec_param )
{
    videotoolbox_private 	*videotoolbox_data;
    pjmedia_vid_codec_param	*param;
    pjmedia_h264_packetizer_cfg  pktz_cfg;
    pjmedia_vid_codec_h264_fmtp  h264_fmtp;
    //  SEncParamExt	 eprm;
    //  SSpatialLayerConfig *elayer = &eprm.sSpatialLayers[0];
    //  SLayerPEncCtx	 elayer_ctx;
    //  SDecodingParam	 sDecParam = {0};
    // int 		 rc;
    pj_status_t		 status;

    PJ_ASSERT_RETURN(codec && codec_param, PJ_EINVAL);

    PJ_LOG(5,(THIS_FILE, "Opening codec.."));

    videotoolbox_data = (videotoolbox_private*) codec->codec_data;
    videotoolbox_data->prm = pjmedia_vid_codec_param_clone( videotoolbox_data->pool,
        codec_param);
    param = videotoolbox_data->prm;

    /* Parse remote fmtp */
    pj_bzero(&h264_fmtp, sizeof(h264_fmtp));
    status = pjmedia_vid_codec_h264_parse_fmtp(&param->enc_fmtp, &h264_fmtp);
    if (status != PJ_SUCCESS)
        return status;

    /* Apply SDP fmtp to format in codec param */
    if (!param->ignore_fmtp) {
        status = pjmedia_vid_codec_h264_apply_fmtp(param);
        if (status != PJ_SUCCESS)
            return status;
    }

    pj_bzero(&pktz_cfg, sizeof(pktz_cfg));
    pktz_cfg.mtu = param->enc_mtu;
    /* Packetization mode */
#if 0
    if (h264_fmtp.packetization_mode == 0)
        pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL;
    else if (h264_fmtp.packetization_mode == 1)
        pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
    else
        return PJ_ENOTSUP;
#else
    if (h264_fmtp.packetization_mode!=
        PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL &&
        h264_fmtp.packetization_mode!=
        PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED)
    {
        return PJ_ENOTSUP;
    }
    /* Better always send in single NAL mode for better compatibility */
    pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL;
#endif

    status = pjmedia_h264_packetizer_create(videotoolbox_data->pool, &pktz_cfg,
        &videotoolbox_data->pktz);
    if (status != PJ_SUCCESS)
        return status;

    videotoolbox_data->whole = (param->packing == PJMEDIA_VID_PACKING_WHOLE);

    /*
     * Encoder
     */

    /* Init encoder parameters */
    //    pj_bzero(&eprm, sizeof(eprm));
    //    eprm.iInputCsp			= videoFormatI420;
    //    eprm.sSpatialLayers[0].uiProfileIdc	= PRO_BASELINE;
    //    eprm.iPicWidth			= param->enc_fmt.det.vid.size.w;
    //    eprm.iPicHeight			= param->enc_fmt.det.vid.size.h;
    //    eprm.fMaxFrameRate			= (param->enc_fmt.det.vid.fps.num *
    // 					   1.0f /
    // 					   param->enc_fmt.det.vid.fps.denum);
    //    eprm.uiFrameToBeCoded		= (unsigned int) -1;
    //    eprm.iTemporalLayerNum		= 1;
    //    eprm.uiIntraPeriod			= 0; /* I-Frame interval in frames */
    //    eprm.bEnableSpsPpsIdAddition	= (oh264_data->whole? false : true);
    //    eprm.bEnableFrameCroppingFlag	= true;
    //    eprm.iLoopFilterDisableIdc		= 0;
    //    eprm.iLoopFilterAlphaC0Offset	= 0;
    //    eprm.iLoopFilterBetaOffset		= 0;
    //    eprm.iMultipleThreadIdc		= 1;
    //    //eprm.bEnableRc			= 1;
    //    eprm.iTargetBitrate			= param->enc_fmt.det.vid.avg_bps;
    //    eprm.bEnableFrameSkip		= 1;
    //    eprm.bEnableDenoise			= 0;
    //    eprm.bEnableSceneChangeDetect	= 1;
    //    eprm.bEnableBackgroundDetection	= 1;
    //    eprm.bEnableAdaptiveQuant		= 1;
    //    eprm.bEnableLongTermReference	= 0;
    //    eprm.iLtrMarkPeriod			= 30;
    //    eprm.bPrefixNalAddingCtrl		= false;
    //    eprm.iSpatialLayerNum		= 1;
    //    if (!oh264_data->whole) {
    // 	eprm.uiMaxNalSize			= param->enc_mtu;
    //    }
    //
    //    pj_bzero(&elayer_ctx, sizeof (SLayerPEncCtx));
    //    elayer_ctx.iDLayerQp		= 24;
    //    elayer_ctx.sSliceCfg.uiSliceMode	= (oh264_data->whole ?
    // 					    SM_SINGLE_SLICE : SM_DYN_SLICE);
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceSizeConstraint = param->enc_mtu;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceNum      = 1;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[0] = 960;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[1] = 0;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[2] = 0;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[3] = 0;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[4] = 0;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[5] = 0;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[6] = 0;
    //    elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[7] = 0;
    //
    //    elayer->iVideoWidth			= eprm.iPicWidth;
    //    elayer->iVideoHeight		= eprm.iPicHeight;
    //    elayer->fFrameRate			= eprm.fMaxFrameRate;
    //    elayer->uiProfileIdc		= eprm.sSpatialLayers[0].uiProfileIdc;
    //    elayer->iSpatialBitrate		= eprm.iTargetBitrate;
    //    elayer->iDLayerQp			= elayer_ctx.iDLayerQp;
    //    elayer->sSliceCfg.uiSliceMode	= elayer_ctx.sSliceCfg.uiSliceMode;
    //
    //    memcpy( &elayer->sSliceCfg,
    //            &elayer_ctx.sSliceCfg,
    //            sizeof (SSliceConfig));
    //    memcpy( &elayer->sSliceCfg.sSliceArgument.uiSliceMbNum[0],
    //            &elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[0],
    //            sizeof (elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum));
    //
    //    /* Init input picture */
    //    oh264_data->esrc_pic->iColorFormat	= videoFormatI420;
    //    oh264_data->esrc_pic->uiTimeStamp	= 0;
    //    oh264_data->esrc_pic->iPicWidth	= eprm.iPicWidth;
    //    oh264_data->esrc_pic->iPicHeight	= eprm.iPicHeight;
    //    oh264_data->esrc_pic->iStride[0] 	= oh264_data->esrc_pic->iPicWidth;
    //    oh264_data->esrc_pic->iStride[1]	=
    // 	    oh264_data->esrc_pic->iStride[2] =
    // 					  oh264_data->esrc_pic->iStride[0]>>1;
    //
    //    oh264_data->enc_input_size = oh264_data->esrc_pic->iPicWidth *
    // 				 oh264_data->esrc_pic->iPicHeight * 3 >> 1;
    //
    //    /* Initialize encoder */
    //    rc = oh264_data->enc->InitializeExt (&eprm);
    //    if (rc != cmResultSuccess) {
    // 	PJ_LOG(4,(THIS_FILE, "SVC encoder Initialize failed, rc=%d", rc));
    // 	return PJMEDIA_CODEC_EFAILED;
    //    }
    //
    //    /*
    //     * Decoder
    //     */
    //    sDecParam.sVideoProperty.size	= sizeof (sDecParam.sVideoProperty);
    //    sDecParam.iOutputColorFormat	= videoFormatI420;
    //    sDecParam.uiTargetDqLayer		= (pj_uint8_t) - 1;
    //    sDecParam.uiEcActiveFlag		= 1;
    //    sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT;
    //
    //    //TODO:
    //    // Apply "sprop-parameter-sets" here
    //
    //    rc = WelsCreateDecoder(&oh264_data->dec);
    //    if (rc) {
    // 	PJ_LOG(4,(THIS_FILE, "Unable to create OpenH264 decoder"));
    // 	return PJMEDIA_CODEC_EFAILED;
    //    }
    //
    //    rc = oh264_data->dec->Initialize (&sDecParam);
    //    if (rc) {
    // 	PJ_LOG(4,(THIS_FILE, "Decoder initialization failed, rc=%d", rc));
    // 	return PJMEDIA_CODEC_EFAILED;
    //    }
    //
    //    pj_int32_t color_fmt = videoFormatI420;
    //    rc = oh264_data->dec->SetOption (DECODER_OPTION_DATAFORMAT,  &color_fmt);
    //    if (rc) {
    // 	PJ_LOG(4,(THIS_FILE,
    // 		 "Warning: SetOption(DECODER_OPTION_DATAFORMAT) failed, rc=%d",
    // 		 rc));
    //    }

    videotoolbox_data->dec_buf_size = (MAX_RX_WIDTH * MAX_RX_HEIGHT * 3 >> 1) +
        (MAX_RX_WIDTH);
    videotoolbox_data->dec_buf = (pj_uint8_t*)pj_pool_alloc(videotoolbox_data->pool,
        videotoolbox_data->dec_buf_size);

    /* Need to update param back after values are negotiated */
    pj_memcpy(codec_param, param, sizeof(*codec_param));

    return PJ_SUCCESS;
}


/*
 * Close codec.
 */
static pj_status_t videotoolbox_codec_close(pjmedia_vid_codec *codec)
{
    PJ_ASSERT_RETURN(codec, PJ_EINVAL);
    PJ_UNUSED_ARG(codec);
    return PJ_SUCCESS;
}

/*
 * Modify codec settings.
 */
static pj_status_t videotoolbox_codec_modify(pjmedia_vid_codec *codec,
    const pjmedia_vid_codec_param *param)
{
    PJ_ASSERT_RETURN(codec && param, PJ_EINVAL);
    PJ_UNUSED_ARG(codec);
    PJ_UNUSED_ARG(param);
    return PJ_EINVALIDOP;
}

static pj_status_t videotoolbox_codec_get_param(pjmedia_vid_codec *codec,
    pjmedia_vid_codec_param *param)
{
    struct videotoolbox_private *videotoolbox_data;

    PJ_ASSERT_RETURN(codec && param, PJ_EINVAL);

    videotoolbox_data = (videotoolbox_private*) codec->codec_data;
    pj_memcpy(param, videotoolbox_data->prm, sizeof(*param));

    return PJ_SUCCESS;
}

static pj_status_t videotoolbox_codec_encode_begin(pjmedia_vid_codec *codec,
    const pjmedia_vid_encode_opt *opt,
    const pjmedia_frame *input,
    unsigned out_size,
    pjmedia_frame *output,
    pj_bool_t *has_more)
{
    struct videotoolbox_private *videotoolbox_data;
    // int rc;

    PJ_ASSERT_RETURN(codec && input && out_size && output && has_more,
        PJ_EINVAL);

    videotoolbox_data = (videotoolbox_private*) codec->codec_data;

    PJ_ASSERT_RETURN(input->size == videotoolbox_data->enc_input_size,
        PJMEDIA_CODEC_EFRMINLEN);

    if (opt && opt->force_keyframe) {
        //关键帧
        // 	oh264_data->enc->ForceIntraFrame(true);
    }

    //  videotoolbox_data->esrc_pic->pData[0] = (pj_uint8_t*)input->buf;
    //  videotoolbox_data->esrc_pic->pData[1] = videotoolbox_data->esrc_pic->pData[0] +
    // 				(videotoolbox_data->esrc_pic->iPicWidth *
    // 				 videotoolbox_data->esrc_pic->iPicHeight);
    //  videotoolbox_data->esrc_pic->pData[2] = videotoolbox_data->esrc_pic->pData[1] +
    // 				(videotoolbox_data->esrc_pic->iPicWidth *
    // 				 videotoolbox_data->esrc_pic->iPicHeight >>2);

    //    pj_memset (&videotoolbox_data->bsi, 0, sizeof (SFrameBSInfo));
    //    rc = videotoolbox_data->enc->EncodeFrame( videotoolbox_data->esrc_pic, &oh264_data->bsi);
    //    if (rc != cmResultSuccess) {
    // 	PJ_LOG(5,(THIS_FILE, "EncodeFrame() error, ret: %d", rc));
    // 	return PJMEDIA_CODEC_EFAILED;
    //    }

    // if (videotoolbox_data->bsi.eOutputFrameType == videoFrameTypeSkip) {
    //     output->size = 0;
    //     output->type = PJMEDIA_FRAME_TYPE_NONE;
    //     output->timestamp = input->timestamp;
    //     return PJ_SUCCESS;
    // }

    // videotoolbox_data->ets = input->timestamp;
    // videotoolbox_data->ilayer = 0;
    // videotoolbox_data->enc_frame_size = videotoolbox_data->enc_processed = 0;

    //    if (videotoolbox_data->whole) {
    //        SLayerBSInfo* pLayerBsInfo;
    //        pj_uint8_t *payload;
    //        unsigned i, payload_size = 0;
    //
    //        *has_more = PJ_FALSE;
    //
    //        /* Find which layer with biggest payload */
    //        videotoolbox_data->ilayer = 0;
    //        payload_size = videotoolbox_data->bsi.sLayerInfo[0].iNalLengthInByte[0];
    //        for (i=0; i < (unsigned)videotoolbox_data->bsi.iLayerNum; ++i) {
    //            unsigned j;
    //            pLayerBsInfo = &videotoolbox_data->bsi.sLayerInfo[i];
    //            for (j=0; j < (unsigned)pLayerBsInfo->iNalCount; ++j) {
    //                if (pLayerBsInfo->iNalLengthInByte[j] > (int)payload_size) {
    //                    payload_size = pLayerBsInfo->iNalLengthInByte[j];
    //                    videotoolbox_data->ilayer = i;
    //                }
    //            }
    //        }
    //
    //        pLayerBsInfo = &videotoolbox_data->bsi.sLayerInfo[videotoolbox_data->ilayer];
    //        if (pLayerBsInfo == NULL) {
    //            output->size = 0;
    //            output->type = PJMEDIA_FRAME_TYPE_NONE;
    //            return PJ_SUCCESS;
    //        }
    //
    //        payload = pLayerBsInfo->pBsBuf;
    //        payload_size = 0;
    //        for (int inal = pLayerBsInfo->iNalCount - 1; inal >= 0; --inal) {
    //            payload_size += pLayerBsInfo->iNalLengthInByte[inal];
    //        }
    //
    //        if (payload_size > out_size)
    //            return PJMEDIA_CODEC_EFRMTOOSHORT;
    //
    //        output->type = PJMEDIA_FRAME_TYPE_VIDEO;
    //        output->size = payload_size;
    //        output->timestamp = input->timestamp;
    //        pj_memcpy(output->buf, payload, payload_size);
    //
    //        return PJ_SUCCESS;
    //    }
    //
    //    return videotoolbox_codec_encode_more(codec, out_size, output, has_more);
    return PJ_SUCCESS;
}


static pj_status_t videotoolbox_codec_encode_more(pjmedia_vid_codec *codec,
    unsigned out_size,
    pjmedia_frame *output,
    pj_bool_t *has_more)
{
    struct videotoolbox_private *videotoolbox_data;
    // const pj_uint8_t *payload;
    // pj_size_t payload_len;
    // pj_status_t status;

    PJ_ASSERT_RETURN(codec && out_size && output && has_more,
        PJ_EINVAL);

    videotoolbox_data = (videotoolbox_private*) codec->codec_data;

    // if (videotoolbox_data->enc_processed < videotoolbox_data->enc_frame_size) {
    //     /* We have outstanding frame in packetizer */
    //     status = pjmedia_h264_packetize(videotoolbox_data->pktz,
    //         videotoolbox_data->enc_frame_whole,
    //         videotoolbox_data->enc_frame_size,
    //         &videotoolbox_data->enc_processed,
    //         &payload, &payload_len);
    //     if (status != PJ_SUCCESS) {
    //         /* Reset */
    //         videotoolbox_data->enc_frame_size = videotoolbox_data->enc_processed = 0;
    //         *has_more = (videotoolbox_data->enc_processed <
    //             videotoolbox_data->enc_frame_size) ||
    //             (videotoolbox_data->ilayer < videotoolbox_data->bsi.iLayerNum);
    //
    //         PJ_PERROR(3,(THIS_FILE, status, "pjmedia_h264_packetize() error"));
    //         return status;
    //     }
    //
    //     PJ_ASSERT_RETURN(payload_len <= out_size, PJMEDIA_CODEC_EFRMTOOSHORT);
    //
    //     output->type = PJMEDIA_FRAME_TYPE_VIDEO;
    //     pj_memcpy(output->buf, payload, payload_len);
    //     output->size = payload_len;
    //
    //     if (videotoolbox_data->bsi.eOutputFrameType == videoFrameTypeIDR) {
    //         output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME;
    //     }
    //
    //     *has_more = (videotoolbox_data->enc_processed < videotoolbox_data->enc_frame_size) ||
    //         (videotoolbox_data->ilayer < videotoolbox_data->bsi.iLayerNum);
    //     return PJ_SUCCESS;
    // }

    // if (videotoolbox_data->ilayer >= videotoolbox_data->bsi.iLayerNum) {
    //     /* No more unretrieved frame */
    //     goto no_frame;
    // }

    //    SLayerBSInfo* pLayerBsInfo;
    //    pLayerBsInfo = &videotoolbox_data->bsi.sLayerInfo[videotoolbox_data->ilayer++];
    //    if (pLayerBsInfo == NULL) {
    //        goto no_frame;
    //    }
    //
    //    videotoolbox_data->enc_frame_size = 0;
    //    for (int inal = pLayerBsInfo->iNalCount - 1; inal >= 0; --inal) {
    //        videotoolbox_data->enc_frame_size += pLayerBsInfo->iNalLengthInByte[inal];
    //    }
    //
    //    videotoolbox_data->enc_frame_whole = pLayerBsInfo->pBsBuf;
    //    videotoolbox_data->enc_processed = 0;
    //
    //
    //    status = pjmedia_h264_packetize(videotoolbox_data->pktz,
    //        videotoolbox_data->enc_frame_whole,
    //        videotoolbox_data->enc_frame_size,
    //        &videotoolbox_data->enc_processed,
    //        &payload, &payload_len);
    //    if (status != PJ_SUCCESS) {
    //        /* Reset */
    //        videotoolbox_data->enc_frame_size = videotoolbox_data->enc_processed = 0;
    //        *has_more = (videotoolbox_data->ilayer < videotoolbox_data->bsi.iLayerNum);
    //
    //        PJ_PERROR(3,(THIS_FILE, status, "pjmedia_h264_packetize() error [2]"));
    //        return status;
    //    }
    //
    //    PJ_ASSERT_RETURN(payload_len <= out_size, PJMEDIA_CODEC_EFRMTOOSHORT);
    //
    //    output->type = PJMEDIA_FRAME_TYPE_VIDEO;
    //    pj_memcpy(output->buf, payload, payload_len);
    //    output->size = payload_len;
    //
    //    if (videotoolbox_data->bsi.eOutputFrameType == videoFrameTypeIDR) {
    //        output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME;
    //    }
    //
    //    *has_more = (videotoolbox_data->enc_processed < videotoolbox_data->enc_frame_size) ||
    //        (videotoolbox_data->ilayer < videotoolbox_data->bsi.iLayerNum);
    //
    //    return PJ_SUCCESS;
    //
    //no_frame:
    //    *has_more = PJ_FALSE;
    //    output->size = 0;
    //    output->type = PJMEDIA_FRAME_TYPE_NONE;
    return PJ_SUCCESS;
}


static pj_status_t videotoolbox_codec_decode(pjmedia_vid_codec *codec,
    pj_size_t count,
    pjmedia_frame packets[],
    unsigned out_size,
    pjmedia_frame *output)
{
    struct videotoolbox_private *videotoolbox_data;
    // unsigned char* pData[3] = {NULL};
    // const pj_uint8_t nal_start[] = { 0, 0, 1 };
    // // SBufferInfo sDstBufInfo;
    // pj_bool_t has_frame = PJ_FALSE;
    // unsigned buf_pos, whole_len = 0;
    // unsigned i, frm_cnt;
    // pj_status_t status = PJ_SUCCESS;

    PJ_ASSERT_RETURN(codec && count && packets && out_size && output,PJ_EINVAL);
    PJ_ASSERT_RETURN(output->buf, PJ_EINVAL);

    videotoolbox_data = (videotoolbox_private*) codec->codec_data;

    return PJ_SUCCESS;
    /*
     * Step 1: unpacketize the packets/frames
     */
//    whole_len = 0;
//    if (videotoolbox_data->whole) {
//        for (i=0; i<count; ++i) {
//            if (whole_len + packets[i].size > videotoolbox_data->dec_buf_size) {
//                PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [1]"));
//                return PJMEDIA_CODEC_EFRMTOOSHORT;
//            }
//
//            pj_memcpy( videotoolbox_data->dec_buf + whole_len,
//                (pj_uint8_t*)packets[i].buf,
//                packets[i].size);
//            whole_len += packets[i].size;
//        }
//
//    }
//    else
//    {
//        for (i=0; i<count; ++i) {
//
//            if (whole_len + packets[i].size + sizeof(nal_start) >
//                videotoolbox_data->dec_buf_size)
//            {
//                PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [1]"));
//                return PJMEDIA_CODEC_EFRMTOOSHORT;
//            }
//
//            status = pjmedia_h264_unpacketize( videotoolbox_data->pktz,
//                (pj_uint8_t*)packets[i].buf,
//                packets[i].size,
//                videotoolbox_data->dec_buf,
//                videotoolbox_data->dec_buf_size,
//                &whole_len);
//            if (status != PJ_SUCCESS) {
//                PJ_PERROR(4,(THIS_FILE, status, "Unpacketize error"));
//                continue;
//            }
//        }
//    }
//
//    if (whole_len + sizeof(nal_start) > videotoolbox_data->dec_buf_size) {
//        PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [2]"));
//        return PJMEDIA_CODEC_EFRMTOOSHORT;
//    }
//
//    /* Dummy NAL sentinel */
//    pj_memcpy( videotoolbox_data->dec_buf + whole_len, nal_start, sizeof(nal_start));
//
//    /*
//     * Step 2: parse the individual NAL and give to decoder
//     */
//    buf_pos = 0;
//    for ( frm_cnt=0; ; ++frm_cnt) {
//        unsigned frm_size;
//        unsigned char *start;
//
//        for (i = 0; buf_pos + i < whole_len; i++) {
//            if (videotoolbox_data->dec_buf[buf_pos + i] == 0 &&
//                videotoolbox_data->dec_buf[buf_pos + i + 1] == 0 &&
//                videotoolbox_data->dec_buf[buf_pos + i + 2] == 1 &&
//                i > 1)
//            {
//                break;
//            }
//        }
//        frm_size = i;
//
//        pj_bzero( pData, sizeof(pData));
//        // pj_bzero( &sDstBufInfo, sizeof (SBufferInfo));
//
//        start = videotoolbox_data->dec_buf + buf_pos;
//
//        /* Decode */
//        videotoolbox_data->dec->DecodeFrame2( start, frm_size, pData, &sDstBufInfo);
//
//        if (sDstBufInfo.iBufferStatus == 1) {
//            /* May overwrite existing frame but that's ok. */
//            status = oh264_got_decoded_frame(codec, videotoolbox_data, pData,
//                &sDstBufInfo,
//                &packets[0].timestamp, out_size,
//                output);
//            has_frame = (status==PJ_SUCCESS && output->size != 0);
//        }
//
//        if (buf_pos + frm_size >= whole_len)
//            break;
//
//        buf_pos += frm_size;
//    }
//
//    /* Signal that we have no more frames */
//    pj_int32_t iEndOfStreamFlag = true;
//    videotoolbox_data->dec->SetOption( DECODER_OPTION_END_OF_STREAM,
//        (void*)&iEndOfStreamFlag);
//
//    /* Retrieve the decoded frame */
//    pj_bzero(pData, sizeof(pData));
//    pj_bzero(&sDstBufInfo, sizeof (SBufferInfo));
//    videotoolbox_data->dec->DecodeFrame2 (NULL, 0, pData, &sDstBufInfo);
//
//    if (sDstBufInfo.iBufferStatus == 1) {
//        /* Overwrite existing output frame and that's ok, because we assume
//         * newer frame have better quality because it has more NALs
//         */
//        status = oh264_got_decoded_frame(codec, videotoolbox_data, pData,
//            &sDstBufInfo, &packets[0].timestamp,
//            out_size, output);
//        has_frame = (status==PJ_SUCCESS && output->size != 0);
//    }
//
//    if (!has_frame) {
//        pjmedia_event event;
//
//        /* Broadcast missing keyframe event */
//        pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_MISSING,
//            &packets[0].timestamp, codec);
//        pjmedia_event_publish(NULL, codec, &event,
//            PJMEDIA_EVENT_PUBLISH_DEFAULT);
//
//        PJ_LOG(5,(THIS_FILE, "Decode couldn't produce picture, "
//                "input nframes=%d, concatenated size=%d bytes",
//                count, whole_len));
//
//        output->type = PJMEDIA_FRAME_TYPE_NONE;
//        output->size = 0;
//        output->timestamp = packets[0].timestamp;
//    }
//
//    return status;
}

#endif	/* PJMEDIA_HAS_VIDEOTOOLBOX_VID_CODEC */
